/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.beepmystuff;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.text.TextUtils;
import android.util.Log;

import org.apache.http.client.methods.HttpGet;

/**
 * Background processing for BeepMyStuff - item adding and image fetching.
 *
 * All work is driven off the ScanRegistry, and updates are posted there.
 * 
 * TODO: fix what's going on with InterruptedException here.
 */
public class NetworkThread extends Thread {
  private static final String TAG = "BMS.NetworkThread";
  private static final long INACTIVE_SLEEP_TIME_MS = 10000;
  private final BmsClient bmsClient;
  private final ScanRegistry scanRegistry;
  private final Context context;
  private final BmsHttpClient httpClient;

  /** An object to wait upon when waiting for events. */
  private final Object waiter = new Object();

  /** Whether the thread should shut down.  Mutated under the waiter lock. */
  private boolean stopping;

  /**
   * Constructs a background network thread.
   * The thread doesn't start until start() is called.
   * @param context global application context
   * @param httpClient BMS HTTP client
   * @param bmsClient BMS high-level API client
   * @param scanRegistry registry to read from and post results to
   */
  public NetworkThread(Context context, BmsHttpClient httpClient,
      BmsClient bmsClient, ScanRegistry scanRegistry) {
    this.context = context;
    this.httpClient = httpClient;
    this.bmsClient = bmsClient;
    this.scanRegistry = scanRegistry;
  }

  /**
   * Wakes up the network thread, causing it to start processing if it had
   * previously started sleeping due to having no work.
   */
  public void wakeUp() {
    synchronized (waiter) {
      waiter.notify();
    }
  }

  /**
   * Shuts down the network thread cleanly. Blocks until the thread has stopped.
   */
  public void shutdown() {
    Log.d(TAG, "Stopping background thread");
    synchronized (waiter) {
      stopping = true;
      wakeUp();
    }
    try {
      join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    Log.d(TAG, "Background thread stopped");
  }

  /**
   * Returns whether the thread is being asked to stop.
   */
  private boolean isStopping() {
    synchronized (waiter) {
      return stopping;
    }
  }

  /**
   * Waits for a INACTIVE_SLEEP_TIME_MS, unless interrupted by a wake up.
   */
  private void interruptibleWait() {
    try {
      Log.v(TAG, "Waiting for something to do");
      synchronized (waiter) {
        waiter.wait(INACTIVE_SLEEP_TIME_MS);
      }
    } catch (InterruptedException e) {
      // do nothing
    }
  }

  @Override
  public void run() {
    stopping = false;
    // TODO: maybe rename to BmsSessionHandler?
    while (!isStopping()) {
      waitForNetwork();
      if (isStopping()) {
        break;
      }
      ScanRegistry.Item item = scanRegistry.getNextItemToAdd();
      if (item != null) {
        addItem(item);
        continue;
      }
      item = scanRegistry.getNextItemToGetImage();
      if (item != null) {
        fetchImage(item);
        continue;
      }
      interruptibleWait();
    }
  }

  /**
   * Adds an item to BeepMyStuff.
   * @param item item to add
   */
  private void addItem(ScanRegistry.Item item) {
    Log.i(TAG, "Adding EAN " + item.getEan() + " share: " + item.shareWith());
    BmsClient.AddEanResult result;
    try {
      result = bmsClient.addEan(item.getEan(), item.shareWith());
    } catch (BmsException e) {
      Log.i(TAG, "Add failed (" + (!e.canRetry() ? "fatal" : "non-fatal") + "): " + e.getMessage());
      item.addFailed(e.getMessage(), !e.canRetry());
      return;
    }

    if (TextUtils.isEmpty(result.getErrorMessage())) {
      Log.v(TAG, "Succeeded '" + result.getMessage() + "' - '" + result.getTitle() + "' - '"
          + result.getImageUrl() + "'");
      item.addSucceeded(result.getMessage(), result.getTitle(), result.getImageUrl());
    } else {
      Log.i(TAG, "Add failed (fatal): '" + result.getErrorMessage());
      item.addFailed(result.getErrorMessage(), true);
    }
  }

  /**
   * Fetches the image data for an Item.
   * @param item item to fetch data for
   */
  private void fetchImage(ScanRegistry.Item item) {
    String url = item.getImageUrl();
    Log.i(TAG, "Fetching image: '" + url);

    byte[] data;
    try {
      data = httpClient.getResponseBody(new HttpGet(url));
    } catch(BmsTransportException e) {
      Log.v(TAG, "Image fetch failed " + e.toString());
      item.imageFetchFailed(!e.canRetry());
      return;
    }

    item.imageFetchSucceeded(data);
    Log.i(TAG, "Image fetch succeeded");
  }

  /**
   * Waits until the network is up and available.
   */
  private void waitForNetwork() {
    ConnectivityManager conMgr = (ConnectivityManager) context
        .getSystemService(Context.CONNECTIVITY_SERVICE);
    while (!isStopping()) {
      NetworkInfo activeInfo = conMgr.getActiveNetworkInfo();
      if (activeInfo != null && activeInfo.isConnected()) {
        Log.v(TAG, "Network is up!");
        return;
      }
      // TODO: better than this! cf ConnectivityManager.CONNECTIVITY_ACTION
      interruptibleWait();
    }
  }
}
